import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import java.util.ArrayList;

import javax.swing.*;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLException;
import javax.media.opengl.awt.GLCanvas;
import javax.media.opengl.glu.GLU;
import javax.media.opengl.glu.GLUquadric;
import com.jogamp.opengl.util.FPSAnimator;
import com.jogamp.opengl.util.texture.Texture;
import com.jogamp.opengl.util.texture.TextureCoords;
import com.jogamp.opengl.util.texture.TextureIO;
import static java.awt.event.KeyEvent.VK_A;
import static java.awt.event.KeyEvent.VK_DOWN;
import static java.awt.event.KeyEvent.VK_LEFT;
import static java.awt.event.KeyEvent.VK_PAGE_DOWN;
import static java.awt.event.KeyEvent.VK_PAGE_UP;
import static java.awt.event.KeyEvent.VK_RIGHT;
import static java.awt.event.KeyEvent.VK_UP;
import static java.awt.event.KeyEvent.VK_Z;
import static javax.media.opengl.GL.*;  // GL constants
import static javax.media.opengl.GL2.*; // GL2 constants
import static javax.media.opengl.GL2ES1.GL_CLIP_PLANE0;
import static javax.media.opengl.GL2ES1.GL_TEXTURE_GEN_MODE;
import static javax.media.opengl.fixedfunc.GLLightingFunc.GL_AMBIENT;
import static javax.media.opengl.fixedfunc.GLLightingFunc.GL_DIFFUSE;
import static javax.media.opengl.fixedfunc.GLLightingFunc.GL_LIGHT0;
import static javax.media.opengl.fixedfunc.GLLightingFunc.GL_LIGHTING;
import static javax.media.opengl.fixedfunc.GLLightingFunc.GL_POSITION;
import static javax.media.opengl.fixedfunc.GLLightingFunc.GL_SMOOTH;


/**
 * The class that implements the jogl 3D API.
 * Created 27/apr/2013.
 * @author Pietro Bolognesi matr. 227383
 * @author Giorgio Lazzaretti matr. 220232
 * 
 * 
 *        
 */
public class GlWorld3D implements GLEventListener, KeyListener {
   private static String TITLE = "!!World!!";
   private static final int CANVAS_WIDTH = 640;  // width of the drawable
   private static final int CANVAS_HEIGHT = 480; // height of the drawable
   private static final int FPS = 60; // animator's target frames per second
   
   private GLU glu;  // for the GL Utility
   
   private float rotateAngleX = 0.0f;   // Rotation angle about x-axis in degree
   private float rotateAngleY = 0.0f;   // Rotation angle about y-axis in degree
   private float rotateSpeedX = 0.0f;   // Rotation speed about x-axis
   private float rotateSpeedY = 0.0f;   // Rotation speed about y-axis
   private float z = -7.0f; // Depth into the screen (z-axis)
   @SuppressWarnings("unused")
   private float height = 2.0f; // Height of ball from floor

   // Quadric for drawing spheres
   private GLUquadric quadric;

   // 3 textures
   private Texture[] textures = new Texture[3];
   private String[] textureFileNames = { 
         "images/envwall.jpg", "images/ball.jpg", "images/envroll.jpg" };
   private String textureFileType = ".jpg";

   // Texture image flips vertically. Shall use TextureCoords class to retrieve the
   // top, bottom, left and right coordinates.
   private float[] textureTops    = new float[3];
   private float[] textureBottoms = new float[3];
   private float[] textureLefts   = new float[3];
   private float[] textureRights  = new float[3];

   private float[] lightAmbientValue = { 0.7f, 0.7f, 0.7f, 1.0f }; // Ambient light value in RGBA
   private float[] lightDiffuseValue = { 1.0f, 1.0f, 1.0f, 1.0f }; // Diffuse light value in RGBA
   private float[] lightDiffusePosition = { 4.0f, 4.0f, 6.0f, 1.0f }; // Position of the light XYZ
   /**
 * Animator
 */
public static FPSAnimator animator;
   /**
 * The list that contains the objects generated.
 */
public static ArrayList<GenericGlObject> objectList;


   /**
 * The main function of the class.
 *
 * @param args
 */
public static void main(String[] args) {
	 
	  //Esempio di costruzione di mille quadrati e sfere
	  objectList = new ArrayList <GenericGlObject> ();
//	  int id = 0;
//	  for(int k = 0; k < 10 ;k++)
//		  for(int j = 0; j < 10; j++)
//			  for (int i = 0; i < 10; i++){
//				  GenericGlObject obj;
//				  if(i%2==0){
//					  obj = new GlCube();
//				  		((GlCube)obj).SetCubeDim(0.05f);
//				  }else{ 
//					  obj = new GlSphere();
//					  ((GlSphere)obj).SetRadius(0.05f);
//				  }
//				  obj.SetId(id);
//				  obj.SetPos(i*2/10.0f, k*2/10.0f, j*2/10.0f);
//				  AddGenericObject(obj);
//				  id++;
//			  }
//	  
//	  //Esempio per come recuperare un oggetto dalla lista
//	  GetGlObjectById(0).SetPos(10.0f, 10.0f, 0);
//	  GetGlObjectById(1).SetPos(10.0f, 20.0f, 0);
//	  GetGlObjectById(1).SetObjectColor(1.0f, 0.0f, 0.0f);
//	   
	   
	 
	   
	   
      // Create the OpenGL rendering canvas
      GLCanvas canvas = new GLCanvas();  // heavy-weight GLCanvas
      canvas.setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));
      GlWorld3D renderer = new GlWorld3D();
      canvas.addGLEventListener(renderer);

      canvas.addKeyListener(renderer);
      canvas.setFocusable(true);  // To receive key event
      canvas.requestFocus();

      // Create a animator that drives canvas' display() at the specified FPS. 
      animator = new FPSAnimator(canvas, FPS, true);
      
      // Create the top-level container frame
      final JFrame frame = new JFrame(); // Swing's JFrame or AWT's Frame
      frame.getContentPane().add(canvas);
      frame.addWindowListener(new WindowAdapter() {
         @Override 
         public void windowClosing(WindowEvent e) {
            // Use a dedicate thread to run the stop() to ensure that the
            // animator stops before program exits.
            new Thread() {
               @Override 
               public void run() {
                  animator.stop(); // stop the animator loop
                  System.exit(0);
               }
            }.start();
         }
      });
      frame.setTitle(TITLE);
      frame.pack();
      frame.setVisible(true);
      animator.start(); // start the animation loop
   }
   
   // ------ Implement methods declared in GLEventListener ------

   /**
    *Initialize the window
    */
   public void init3D() {
	   		objectList = new ArrayList <GenericGlObject> ();
	   
	      // Create the OpenGL rendering canvas
	      GLCanvas canvas = new GLCanvas();  // heavy-weight GLCanvas
	      canvas.setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));
	      GlWorld3D renderer = new GlWorld3D();
	      canvas.addGLEventListener(renderer);

	      canvas.addKeyListener(renderer);
	      canvas.setFocusable(true);  // To receive key event
	      canvas.requestFocus();

	      // Create a animator that drives canvas' display() at the specified FPS. 
	      animator = new FPSAnimator(canvas, FPS, true);
	      
	      // Create the top-level container frame
	      final JFrame frame = new JFrame(); // Swing's JFrame or AWT's Frame
	      frame.getContentPane().add(canvas);
	      frame.addWindowListener(new WindowAdapter() {
	         @Override 
	         public void windowClosing(WindowEvent e) {
	            // Use a dedicate thread to run the stop() to ensure that the
	            // animator stops before program exits.
	            new Thread() {
	               @Override 
	               public void run() {
	                  animator.stop(); // stop the animator loop
	                  System.exit(0);
	               }
	            }.start();
	         }
	      });
	      frame.setTitle(TITLE);
	      frame.pack();
	      frame.setVisible(true);
	      animator.start(); // start the animation loop
	   
	   
	   
//      
   }

   /**
    * Call-back handler for window re-size event. Also called when the drawable is 
    * first set to visible.
    */
   @Override
   public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
      GL2 gl = drawable.getGL().getGL2();  // get the OpenGL 2 graphics context

      if (height == 0) height = 1;   // prevent divide by zero
      float aspect = (float)width / height;

      // Set the view port (display area) to cover the entire window
      gl.glViewport(0, 0, width, height);

      // Setup perspective projection, with aspect ratio matches viewport
      gl.glMatrixMode(GL_PROJECTION);  // choose projection matrix
      gl.glLoadIdentity();             // reset projection matrix
      this.glu.gluPerspective(45.0, aspect, 0.1, 100.0); // fovy, aspect, zNear, zFar
      // Enable the model-view transform
      gl.glMatrixMode(GL_MODELVIEW);
      gl.glLoadIdentity(); // reset
   }

   /**
    * Called back by the animator to perform rendering.
    */
   @Override
   public void display(GLAutoDrawable drawable) {
      GL2 gl = drawable.getGL().getGL2();  // get the OpenGL 2 graphics context
      gl.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear color and depth buffers
      gl.glLoadIdentity();  // reset the model-view matrix

      // Clip plane equations for clipping the reflected image
      double eqr[] = { 0.0f, -1.0f, 0.0f, 0.0f };

      // Translate downward to see the floor, and z
      gl.glTranslatef(0.0f, -0.6f, this.z);
      
      
      
      // Set the color mask RGBA to false, i.e., no color gets thru
      gl.glColorMask(false, false, false, false);

      // Setting up the stencil buffer and stencil testing
      gl.glEnable(GL_STENCIL_TEST);      // Enable stencil buffer for "marking" the floor
      gl.glStencilFunc(GL_ALWAYS, 1, 1); // Always passes, 1 Bit Plane, 1 As Mask
      gl.glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); // Set the stencil buffer to 1 where we draw any polygon
      // Keep if test fails, keep if test passes but buffer test fails
      // replace if test passes
      gl.glDisable(GL_DEPTH_TEST); // Disable depth testing

      drawFloor(gl);

      gl.glEnable(GL_DEPTH_TEST);  // Enable depth testing
      gl.glColorMask(true, true, true, true); // Set color mask to let RGBA thru
      gl.glStencilFunc(GL_EQUAL, 1, 1);       // We draw only where the stencil is 1
      // (i.e. where the floor was drawn)
      gl.glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // Don't change the stencil buffer

      gl.glEnable(GL_CLIP_PLANE0); // Enable clip plane for removing artifacts
                                   // (when the object crosses the floor)
      gl.glClipPlane(GL_CLIP_PLANE0, eqr, 0); // Equation for reflected objects
    
      gl.glDisable(GL_CLIP_PLANE0);  // Disable clip plane for drawing the floor
      gl.glDisable(GL_STENCIL_TEST); // We don't need the stencil buffer any more

      gl.glLightfv(GL_LIGHT0, GL_POSITION, this.lightDiffusePosition, 0); // Set up LIGHT0 position
      gl.glEnable(GL_BLEND);     // Enable blending (otherwise the reflected object won't show)
      gl.glDisable(GL_LIGHTING); // Since we use blending, we disable Lighting
      gl.glColor4f(1.0f, 1.0f, 1.0f, 0.8f); // Set color to white with 80% alpha
      gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Blending based on source alpha and 1 minus dest alpha
      drawFloor(gl); // Draw the floor
      
      
      //Ciclo di renderizzazione degli oggetti
      for(int i = 0; i < objectList.size(); i++){
    	  gl.glEnable(GL_LIGHTING);  // Enable lighting
          //gl.glDisable(GL_BLEND);    // Disable blending
          gl.glPushMatrix();
          float x = objectList.get(i).GetXPos();
          float z = objectList.get(i).GetZPos();
          float y = objectList.get(i).GetYPos();
          gl.glTranslatef(x, y, z); // Position the ball at proper height
          gl.glRotatef(this.rotateAngleX, 1.0f, 0.0f, 0.0f);
          gl.glRotatef(this.rotateAngleY, 0.0f, 1.0f, 0.0f);
          gl.glColor3f(1.0f, 0.0f, 0.0f);
          objectList.get(i).SetGL(gl);
          objectList.get(i).DrawObject();
          gl.glPopMatrix();
    	  
      }
      
      // Update rotational angle 
      this.rotateAngleX += this.rotateSpeedX;
      this.rotateAngleY += this.rotateSpeedY;
      gl.glFlush();
   }

   // Render the floor
   private void drawFloor(GL2 gl) {
      //textures[0].enable(gl);  // "evnwall"
      //textures[0].bind(gl);
      gl.glColor3f(0.0f, 0.0f, 0.8f);
	  gl.glBegin(GL_QUADS);
      gl.glNormal3f(0.0f, 1.0f, 0.0f);   // Normal pointing up
      //gl.glTexCoord2f(textureLefts[0], textureBottoms[0]);  // Bottom-left of texture
      gl.glVertex3f(-2.0f, 0.0f, 2.0f);  // Bottom-Left corner of the floor
      //gl.glTexCoord2f(textureLefts[0], textureTops[0]);     // Top-left of texture
      gl.glVertex3f(-2.0f, 0.0f, -2.0f); // Top-left corner of the floor
      //gl.glTexCoord2f(textureRights[0], textureTops[0]);    // Top-right of texture
      gl.glVertex3f(2.0f, 0.0f, -2.0f);  // Top-right corner of the floor
      //gl.glTexCoord2f(textureRights[0], textureBottoms[0]); // Bottom-right of texture
      gl.glVertex3f(2.0f, 0.0f, 2.0f);  // Bottom-right corner of the floor
      gl.glEnd();
   }

   /** 
    * Called back before the OpenGL context is destroyed. Release resource such as buffers. 
    */
   @Override
   public void dispose(GLAutoDrawable drawable) { 
	//TODO
   }

   @Override
   public void keyPressed(KeyEvent e) {
      switch (e.getKeyCode()) {
         case VK_RIGHT:  // increase rotational speed y
            this.rotateSpeedY += 0.08f;
            break;
         case VK_LEFT:   // decrease rotational speed y
            this.rotateSpeedY -= 0.08f;
            break;
         case VK_DOWN:   // increase rotational speed x
            this.rotateSpeedX += 0.08f;
            break;
         case VK_UP:     // decrease rotational speed x
            this.rotateSpeedX -= 0.08f;
            break;
         case VK_A:      // zoom in
            this.z += 0.05f;
            break;
         case VK_Z:      // zoom out
            this.z -= 0.05f;
            break;
         case VK_PAGE_UP:    // move ball up
        	for (int i = 0; i< objectList.size(); i++){
        		objectList.get(i).SetPos(objectList.get(i).GetXPos(), objectList.get(i).GetYPos() + 0.03f, objectList.get(i).GetZPos());
        	}
            this.height += 0.03f;
            break;
         case VK_PAGE_DOWN:  // move ball down
         	for (int i = 0; i< objectList.size(); i++){
        		objectList.get(i).SetPos(objectList.get(i).GetXPos(), objectList.get(i).GetYPos() - 0.03f, objectList.get(i).GetZPos());
        	}
            this.height -= 0.03f;
            break;
         case 78:    // move ball up
         	for (int i = 0; i< objectList.size(); i++){
         		objectList.get(i).SetPos(objectList.get(i).GetXPos()-0.03f, objectList.get(i).GetYPos(), objectList.get(i).GetZPos());
         	}
             this.height += 0.03f;
             break;
          case 77:  // move ball down
          	for (int i = 0; i< objectList.size(); i++){
         		objectList.get(i).SetPos(objectList.get(i).GetXPos()+0.03f, objectList.get(i).GetYPos(), objectList.get(i).GetZPos());
         	}
             this.height -= 0.03f;
             break;
          case 83:    // move ball up
        	  for (int i = 0; i< objectList.size(); i++){
        		  objectList.get(i).SetPos(objectList.get(i).GetXPos(), objectList.get(i).GetYPos(), objectList.get(i).GetZPos()-0.03f);
        	  }
        	  this.height += 0.03f;
        	  break;
          case 88:  // move ball down
        	  for (int i = 0; i< objectList.size(); i++){
        		  objectList.get(i).SetPos(objectList.get(i).GetXPos(), objectList.get(i).GetYPos(), objectList.get(i).GetZPos()+0.03f);
        	  }
        	  this.height -= 0.03f;
        	  break;
      }
   }


   /**
 * Clear all the objects from the list of the objects
 *
 */
public void ClearObjList(){
	   objectList.clear();
   }
   
   /**
 * add an object to the list of the object, it can be any type of object
 *
 * @param obj the object to add
 */
public static void AddGenericObject(GenericGlObject obj){
	   objectList.add(obj);
   }
   
   /**
 * Create a new Cube and put it into the list of the objects
 *
 * @param Id Id to assign to the object into the list
 * @return GlCube
 */
public GlCube CreateNewGlCube(int Id){
	   GlCube c = new GlCube();
	   c.SetId(Id);
	   objectList.add(c);
	   return c;
   }
   
   /**
 * Create a new Sphere and put it into the list of the objects
 *
 * @param Id Id to assign to the object into the list
 * @return GlSphere
 */
public GlSphere CreateNewGlSphere(int Id){
	   GlSphere s = new GlSphere();
	   s.SetId(Id);
	   objectList.add(s);
	   return s;
   }
   /**
 * Create a new Cylinder and put it into the list of the objects
 *
 * @param Id Id to assign to the object into the list
 * @return GlCilinder
 */
public GlCylinder CreateNewGlCylinder(int Id){
	   GlCylinder s = new GlCylinder();
	   s.SetId(Id);
	   objectList.add(s);
	   return s;
   }
   
   
   
   /**
 * return an object from the list of the object
 *
 * @param Id id of the object to get
 * @return GenericGlObject
 */
public static GenericGlObject GetGlObjectById(int Id){
	   int i;
	   boolean Trovato = false;
	   for (i = 0; i < objectList.size(); i++){
		   if(objectList.get(i).GetId() == Id){
			   Trovato = true;
			   break;
		   }
	   }
	   if (Trovato)
		   return objectList.get(i);
	   
	   return null;
   }
   
   /**
 * remove an object from the list of the objects
 *
 * @param Id id of the object to remove
 */
public void RemoveGlObjectById(int Id){
	   objectList.remove(GetGlObjectById(Id));
   }
   
   
   /**
 * Change the point of view, set the position of the viewer and the point that the viewer is viewing.
 * set also the Up direction of the axes
 *
 * @param x x coord of the viewer
 * @param y y coord of the viewer
 * @param z z coord of the viewer
 * @param Px x coord of the reference point
 * @param Py y coord of the reference point
 * @param Pz z coord of the reference point
 * @param Ux x direction of the up vector
 * @param Uy y direction of the up vector
 * @param Uz z direction of the up vector
 */
public void changePointOfView(float x, float y, float z, float Px,float Py, float Pz, float Ux, float Uy, float Uz) {
	   
   this.glu.gluLookAt(x,y, z, Px, Py, Pz,Ux,Uy,Uz ); 
   
   }

@Override
public void keyReleased(KeyEvent e) {
	// TODO Auto-generated method stub.
	
}

@Override
public void keyTyped(KeyEvent e) {
	// TODO Auto-generated method stub.
	
}

@Override
public void init(GLAutoDrawable drawable) {
	GL2 gl = drawable.getGL().getGL2();      // get the OpenGL graphics context
  this.glu = new GLU();                         // get GL Utilities
  gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // set background (clear) color
  gl.glClearDepth(1.0f);      // set clear depth value to farthest
  gl.glEnable(GL_DEPTH_TEST); // enables depth testing
  gl.glDepthFunc(GL_LEQUAL);  // the type of depth test to do
  gl.glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // best perspective correction
  gl.glShadeModel(GL_SMOOTH); // blends colors nicely, and smoothes out lighting

  // Clear The Stencil Buffer To 0
  gl.glClearStencil(0);

  // Load all the texture images
  try {
     for (int i = 0; i < this.textures.length; i++) {
        this.textures[i] = TextureIO.newTexture(this.getClass().getResource(
              this.textureFileNames[i]), false, this.textureFileType);
        // Use linear filter for texture if image is larger than the original texture
        gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        // Use linear filter for texture if image is smaller than the original texture
        gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

        TextureCoords textureCoords = this.textures[i].getImageTexCoords();
        this.textureTops[i] = textureCoords.top();
        this.textureBottoms[i] = textureCoords.bottom();
        this.textureLefts[i] = textureCoords.left();
        this.textureRights[i] = textureCoords.right();
     }
  } catch (GLException e) {
     e.printStackTrace();
  } catch (IOException e) {
     e.printStackTrace();
  }

  // Set up lighting
  gl.glLightfv(GL_LIGHT0, GL_AMBIENT, this.lightAmbientValue, 0); // Set the ambient lighting for LIGHT0
  gl.glLightfv(GL_LIGHT0, GL_DIFFUSE, this.lightDiffuseValue, 0); // Set the diffuse lighting for LIGHT0
  gl.glLightfv(GL_LIGHT0, GL_POSITION, this.lightDiffusePosition, 0); // Set the position for LIGHT0
  gl.glEnable(GL_LIGHT0);   // Enable LIGHT0
  gl.glEnable(GL_LIGHTING); // Enable Lighting

  // Set up the Quadric for drawing sphere
  this.quadric = this.glu.gluNewQuadric();             // Create a new quadric
  this.glu.gluQuadricNormals(this.quadric, GL_SMOOTH); // Generate smooth normals
  this.glu.gluQuadricTexture(this.quadric, true);      // Enable texture
  // set up sphere mapping for both the s- and t-axes
  gl.glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
  gl.glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
	
}

	   
}
